Add support for a license-file manifest key
authorAlex Crichton <alex@alexcrichton.com>
Tue, 25 Nov 2014 06:18:54 +0000 (22:18 -0800)
committerAlex Crichton <alex@alexcrichton.com>
Tue, 25 Nov 2014 17:33:16 +0000 (09:33 -0800)
This key will support projects with nonstandard licenses and the registry will
display the license as "nonstandard".

Closes #940

src/cargo/core/manifest.rs
src/cargo/ops/cargo_package.rs
src/cargo/ops/registry.rs
src/cargo/util/toml.rs
src/doc/manifest.md
src/registry/lib.rs
tests/test_cargo_package.rs
tests/test_cargo_registry.rs

index 13801b7e293c435a785260c265f25710182eef03..c72ee2313ebce66c959d1987ead2e9c94647eb4a 100644 (file)
@@ -45,6 +45,7 @@ pub struct ManifestMetadata {
     pub authors: Vec<String>,
     pub keywords: Vec<String>,
     pub license: Option<String>,
+    pub license_file: Option<String>,
     pub description: Option<String>,    // not markdown
     pub readme: Option<String>,         // file, not contents
     pub homepage: Option<String>,       // url
index 95bb5a717bf6af3a012d91a08731769f8022acf6..0e6336aad9401d937287c08623395ed008e42102 100644 (file)
@@ -75,15 +75,15 @@ fn check_metadata(pkg: &Package, shell: &mut MultiShell) -> CargoResult<()> {
     let mut missing = vec![];
 
     macro_rules! lacking {
-        ($($field: ident),*) => {{
+        ($( $($field: ident)||* ),*) => {{
             $(
-                if md.$field.as_ref().map_or(true, |s| s.is_empty()) {
-                    missing.push(stringify!($field))
+                if $(md.$field.as_ref().map_or(true, |s| s.is_empty()))&&* {
+                    $(missing.push(stringify!($field).replace("_", "-"));)*
                 }
-                )*
+            )*
         }}
     }
-    lacking!(description, license)
+    lacking!(description, license || license_file)
 
     if !missing.is_empty() {
         let mut things = missing.slice_to(missing.len() - 1).connect(", ");
@@ -91,10 +91,10 @@ fn check_metadata(pkg: &Package, shell: &mut MultiShell) -> CargoResult<()> {
         if !things.is_empty() {
             things.push_str(" or ");
         }
-        things.push_str(*missing.last().unwrap());
+        things.push_str(missing.last().unwrap().as_slice());
 
         try!(shell.warn(
-            format!("Warning: manifest has no {things}. \
+            format!("warning: manifest has no {things}. \
                     See http://doc.crates.io/manifest.html#package-metadata for more info.",
                     things = things).as_slice()))
     }
index e042d0765b12cfe96ef2e6a3849aff2ce0960756..3886b4d6c9775ca19308f124d24d1062c461f467 100644 (file)
@@ -1,5 +1,6 @@
 use std::collections::HashMap;
 use std::io::File;
+use std::io::fs::PathExtensions;
 use std::os;
 use term::color::BLACK;
 
@@ -87,7 +88,7 @@ fn transmit(pkg: &Package, tarball: &Path, registry: &mut Registry)
     let manifest = pkg.get_manifest();
     let ManifestMetadata {
         ref authors, ref description, ref homepage, ref documentation,
-        ref keywords, ref readme, ref repository, ref license,
+        ref keywords, ref readme, ref repository, ref license, ref license_file,
     } = *manifest.get_metadata();
     let readme = match *readme {
         Some(ref readme) => {
@@ -98,6 +99,15 @@ fn transmit(pkg: &Package, tarball: &Path, registry: &mut Registry)
         }
         None => None,
     };
+    match *license_file {
+        Some(ref file) => {
+            if !pkg.get_root().join(file).exists() {
+                return Err(human(format!("the license file `{}` does not exist",
+                                         file)))
+            }
+        }
+        None => {}
+    }
     registry.publish(&NewCrate {
         name: pkg.get_name().to_string(),
         vers: pkg.get_version().to_string(),
@@ -111,6 +121,7 @@ fn transmit(pkg: &Package, tarball: &Path, registry: &mut Registry)
         readme: readme,
         repository: repository.clone(),
         license: license.clone(),
+        license_file: license_file.clone(),
     }, tarball).map_err(|e| {
         human(e.to_string())
     })
index e5a7fb1b4a01e1e349bd90030ad0815dde6314cb..82d24074567c1834b029bfde1dd585821b5551ac 100644 (file)
@@ -267,6 +267,7 @@ pub struct TomlProject {
     readme: Option<String>,
     keywords: Option<Vec<String>>,
     license: Option<String>,
+    license_file: Option<String>,
     repository: Option<String>,
 }
 
@@ -518,6 +519,7 @@ impl TomlManifest {
             readme: project.readme.clone(),
             authors: project.authors.clone(),
             license: project.license.clone(),
+            license_file: project.license_file.clone(),
             repository: project.repository.clone(),
             keywords: project.keywords.clone().unwrap_or(Vec::new()),
         };
@@ -541,6 +543,11 @@ impl TomlManifest {
             manifest.add_warning(format!("         For more information, see \
                                           http://doc.crates.io/build-script.html"));
         }
+        if project.license_file.is_some() && project.license.is_some() {
+            manifest.add_warning(format!("warning: only one of `license` or \
+                                                   `license-file` is necessary"));
+        }
+
         Ok((manifest, nested_paths))
     }
 }
index b25c69e00283af8eade9c17cbc73e6671c46e1c5..f86484958aed557a7df7d350f854fb4cb2016c20 100644 (file)
@@ -82,9 +82,14 @@ keywords = ["...", "..."]
 
 # This is a string description of the license for this package. Currently
 # crates.io will validate the license provided against a whitelist of known
-# license identifiers from http://spdx.org/licenses/. Multiple licenses can 
+# license identifiers from http://spdx.org/licenses/. Multiple licenses can
 # be separated with a `/`
 license = "..."
+
+# If a project is using a nonstandard license, then this key may be specified in
+# lieu of the above key and must point to a file relative to this manifest
+# (similar to the readme key)
+license-file = "..."
 ```
 
 The [crates.io](https://crates.io) registry will render the description, display
index 39f5965a765aefae074070e1cb709bfa8cd7a2e0..d2952ae0c9776f48507a5aebcac1d50fecd1824a 100644 (file)
@@ -55,6 +55,7 @@ pub struct NewCrate {
     pub readme: Option<String>,
     pub keywords: Vec<String>,
     pub license: Option<String>,
+    pub license_file: Option<String>,
     pub repository: Option<String>,
 }
 
index 57f2e64e98b1963524d88d52a07bd4b0e87dd1b8..c3f2eccade03030abea8e8e8d7b2ef14b64f6924 100644 (file)
@@ -80,8 +80,9 @@ test!(metadata_warning {
         verifying = VERIFYING,
         compiling = COMPILING,
         dir = p.url()).as_slice())
-                .with_stderr("Warning: manifest has no description or license. See \
-                              http://doc.crates.io/manifest.html#package-metadata for more info."));
+                .with_stderr("\
+warning: manifest has no description, license or license-file. See \
+http://doc.crates.io/manifest.html#package-metadata for more info."));
 
     let p = project("one")
         .file("Cargo.toml", r#"
@@ -104,8 +105,9 @@ test!(metadata_warning {
         verifying = VERIFYING,
         compiling = COMPILING,
         dir = p.url()).as_slice())
-                .with_stderr("Warning: manifest has no description. See \
-                              http://doc.crates.io/manifest.html#package-metadata for more info."));
+                .with_stderr("\
+warning: manifest has no description. See \
+http://doc.crates.io/manifest.html#package-metadata for more info."));
 
     let p = project("both")
         .file("Cargo.toml", format!(r#"
index 3fc4f7f8eb32b3ffe26548508f45668e7815b1de..366453c7ac4059155a8f34d74d811a4c0329e101 100644 (file)
@@ -480,3 +480,22 @@ test!(login_with_no_cargo_dir {
                        .env("HOME", Some(home)),
                 execs().with_status(0));
 })
+
+test!(bad_license_file {
+    let p = project("all")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+            license-file = "foo"
+            description = "bar"
+        "#)
+        .file("src/main.rs", r#"
+            fn main() {}
+        "#);
+    assert_that(p.cargo_process("publish"),
+                execs().with_status(101)
+                       .with_stderr("\
+the license file `foo` does not exist"));
+})